feat(analytics): World ID unique-verifications data layer (1/3) - #2191
Open
soamdesai-tfh wants to merge 19 commits into
Open
feat(analytics): World ID unique-verifications data layer (1/3)#2191soamdesai-tfh wants to merge 19 commits into
soamdesai-tfh wants to merge 19 commits into
Conversation
Dual v3/v4 action-day rollup tables with cascading FKs, the singleton watermark state table, the tracked return-shape table, the atomic five-minute dual-source rollup function under advisory lock (533214,43), the combined app-daily read function, service-only Hasura metadata, the protected cron route with a rollout-disable gate, operator scripts for the out-of-band v3 index and backfill validation, and the fresh-stack contract harness with its data-layer tests.
soamdesai-tfh
temporarily deployed
to
development
July 31, 2026 04:35 — with
GitHub Actions
Inactive
This was referenced Jul 31, 2026
soamdesai-tfh
temporarily deployed
to
development
July 31, 2026 04:43 — with
GitHub Actions
Inactive
soamdesai-tfh
force-pushed
the
feat/world-id-analytics-data
branch
from
July 31, 2026 07:41
2d9c3d9 to
093f412
Compare
soamdesai-tfh
temporarily deployed
to
development
July 31, 2026 07:41 — with
GitHub Actions
Inactive
soamdesai-tfh
temporarily deployed
to
development
July 31, 2026 08:32 — with
GitHub Actions
Inactive
The default and --million paths invoked integration.test.ts and million.test.ts, which land with the read slice; jest exits 1 on a pattern with no matches, so both entrypoints failed on this branch. The read slice restores them alongside the files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
soamdesai-tfh
temporarily deployed
to
development
July 31, 2026 16:46 — with
GitHub Actions
Inactive
soamdesai-tfh
temporarily deployed
to
development
July 31, 2026 21:24 — with
GitHub Actions
Inactive
soamdesai-tfh
temporarily deployed
to
development
July 31, 2026 21:28 — with
GitHub Actions
Inactive
soamdesai-tfh
temporarily deployed
to
development
August 1, 2026 00:59 — with
GitHub Actions
Inactive
soamdesai-tfh
temporarily deployed
to
development
August 3, 2026 00:49 — with
GitHub Actions
Inactive
soamdesai-tfh
marked this pull request as ready for review
August 3, 2026 00:49
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9f175224c5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
soamdesai-tfh
temporarily deployed
to
development
August 3, 2026 08:00 — with
GitHub Actions
Inactive
A plain CREATE INDEX in the transactional migration blocks nullifier_v4 verification inserts for the build duration while old pods serve during a rolling deploy. Build it out of band with CONCURRENTLY like the v3 twin: the operator script now creates and validity-gates both indexes, and the backfill gate refuses to run until both are valid. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
soamdesai-tfh
temporarily deployed
to
development
August 3, 2026 18:48 — with
GitHub Actions
Inactive
soamdesai-tfh
temporarily deployed
to
development
August 3, 2026 21:39 — with
GitHub Actions
Inactive
…ats_daily The API contract (legacy_actions) and UI scope type (source: "legacy") already named this concept 'legacy'; only the SQL layer said 'v3'. v3 was never a real protocol generation here - nullifier/action predate v4 entirely and internally split by engine (on-chain/cloud), not by any versioning scheme. Align the table/index name and source tags with the terminology the other two layers already settled on. Regenerated graphql.schema.json/graphql.ts against the renamed table; tsc and prettier clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
soamdesai-tfh
force-pushed
the
feat/world-id-analytics-data
branch
from
August 3, 2026 21:43
2cd5cd5 to
3c2ff71
Compare
soamdesai-tfh
temporarily deployed
to
development
August 3, 2026 21:43 — with
GitHub Actions
Inactive
soamdesai-tfh
temporarily deployed
to
development
August 3, 2026 21:54 — with
GitHub Actions
Inactive
The team's concern with the one-shot design: a full-history backfill runs as one unbounded transaction, so a failure loses all progress and nothing reports where it died. rollup_world_id_analytics() now takes max_advance_days, capping how far one call may advance the watermark in whole UTC days (NULL = unbounded). The rebuild start stays derived from the watermark, so no sequence of calls can skip a range. A capped bootstrap from -infinity anchors at the oldest raw row (-infinity + interval is still -infinity), deletes are bounded to the chunk's date range, and once the watermark is inside the 25-hour overlap horizon - where the day-aligned target can land at or below the watermark and would otherwise never advance - the call finishes at the cutoff instead of stalling. backfill_world_id_analytics(chunk_days DEFAULT 1) is a new operator-only procedure that loops the rollup and commits between chunks: an interrupted backfill keeps its progress, a rerun resumes from the committed watermark, and the watermark doubles as a live progress indicator. It aborts loudly on a lock miss and aborts rather than spins if a chunk ever fails to advance the watermark. The deployment gate's phase 1 calls it and no longer auto-resets the watermark; only a parity validation failure requires the documented explicit reset. The cron route passes max_advance_days: 30 - invisible in steady state (~25h window), bounds catch-up after an outage to 30 days per tick - and reports distinct outcomes (advanced with processed_through, lock_missed as a warning, disabled), so a stalled watermark is observable and lock misses are no longer silent successes. Migration 1785459169000 edited in place (unreleased); local stacks that already applied it must be recreated. Fresh-stack data-slice suite 17/17 including a new chunked-rollup suite (cap math, bootstrap anchor, empty-database termination, chunk-boundary counts, committed chunks surviving a failed chunk and resuming); route unit tests 6/6; tsc and prettier clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
soamdesai-tfh
temporarily deployed
to
development
August 4, 2026 23:34 — with
GitHub Actions
Inactive
A capped rebuild anchors at the oldest raw row, so a rolled row whose raw rows were later deleted survives a watermark-only reset and fails parity validation on every retry (reproduced: the gate loops forever). The documented reset now truncates both rollup tables before resetting the watermark, and a new gate case pins the trapped scenario and its recovery. Codex review finding on #2191. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
soamdesai-tfh
temporarily deployed
to
development
August 5, 2026 16:45 — with
GitHub Actions
Inactive
…uild One SQL function rebuilds an explicit UTC date window (delete + recount, idempotent); no dates means the trailing ~25h. The watermark singleton, its bootstrap/clamp logic, and the backfill procedure are gone. The route gains a dated mode (chunked loop, failed_ranges response, fresh service client per chunk) that bypasses the rollout flag for operator backfill and repair; the cron mode stays fail-closed behind it. Advisory lock is now a blocking wait: Hasura hides Postgres SQLSTATEs from non-admin roles, so a try-lock miss was indistinguishable from failure. Cron slows to */15 with matching tolerance. The ops script no longer backfills; it is the index gates plus a self-contained parity check (catch-up inside one repeatable-read snapshot, complete days only) whose mismatch sample names the exact ranges to re-POST — no reset step exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…runbook Window semantics replace the watermark suite (dated ranges, trailing window scoping, 5-minute clamp, orphan sweep, blocking-lock queueing). Parity gate tests now prove coverage (never-backfilled history fails) and repair-by-re-roll. New suites: index build under live writes (25001 in-transaction, 55P03 migration-style blocking, zero lost writes under CONCURRENTLY), cron wiring + dated mode against the real stack, and an end-to-end release runbook — dark deploy, three-POST backfill, parity sign-off, reads per app/environment, a real Hasura-delivered live tick, late-data heal, deletion sweep, steady-state parity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
soamdesai-tfh
temporarily deployed
to
development
August 6, 2026 00:24 — with
GitHub Actions
Inactive
Soam's ruling: hasura/operations/world-id-analytics contains exactly one file — the concurrent index build — because one file is what the platform team runs. backfill-and-validate.sql is deleted; the parity comparison it carried now lives in the test suite (readParityMismatches in canonical-analytics.ts, exercised by rollup-parity.test.ts and the e2e runbook suite), where it already proved coverage, corruption, and orphan-sweep behavior. Operator flow is now: index script, dated backfill POSTs until failed_ranges is empty, flag on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
soamdesai-tfh
temporarily deployed
to
development
August 6, 2026 00:59 — with
GitHub Actions
Inactive
A concurrent action deletion locks the parent row first and then cascades into the stats rows the running window just deleted; the rollup's recount insert then waits on that parent — a lock cycle whose victim is nearly always the user's delete, because the deletion is structurally the earlier waiter and PostgreSQL's detector aborts the process that has waited deadlock_timeout. Reproduced 3/3 on postgres:14 (delete 40P01, rollup committed); the existing race test missed it because a first-ever rollup deletes no child rows. Ordered FOR KEY SHARE pre-locks on the recount's parent actions restore parent-before-child ordering on both sides: the deletion now parks briefly on the parent and cascades cleanly after the window commits — verified 3/3, plus a regression test against a re-rolled window. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
soamdesai-tfh
temporarily deployed
to
development
August 6, 2026 05:32 — with
GitHub Actions
Inactive
Takaros999
reviewed
Aug 7, 2026
Contributor
There was a problem hiding this comment.
This script has a few problems:
- You need to store success/failure for a given chunk otherwise they're going to be retried. you can use redis with a long TTL eg. 2 days
- You expect from /end data to be passed into the endpoint's body. Why? The backfill should happen everytime the cron job runs without a manual operation. after it succeeds we'll just update the code and revert it to the intended state of triggering for the last date
Comment on lines
+64
to
+66
| retry_interval_seconds: 10 | ||
| timeout_seconds: 60 | ||
| tolerance_seconds: 900 |
Contributor
There was a problem hiding this comment.
i would increase the timeout to 14m, so the cron job can make enough progress. if we only process rows 1 minute every 15 minutes this could hours/days.
soamdesai-tfh
temporarily deployed
to
development
August 7, 2026 02:49 — with
GitHub Actions
Inactive
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First of three stacked PRs: single-metric World ID analytics (Unique Verifications across legacy + v4 actions). Data layer.
action_legacy_stats_daily/action_v4_stats_daily+ read fnworld_id_analytics_app_daily()rollup_world_id_analytics(from_date, to_date)— stateless, idempotent delete+recount of a UTC date window; no dates = trailing ~25h; upper bound clamped tonow − 5 min; blocking advisory lock; parent-action pre-locks so a concurrent action deletion waits briefly instead of deadlocking/_rollup-world-id-analytics: empty body = cron mode (fail-closed behindWORLD_ID_ANALYTICS_ROLLUP_ENABLED, Hasura cron*/15);{from_date, to_date, chunk_days?}= operator backfill/repair (secret-gated, ≤92 days/call, 10-day chunks, response listsfailed_rangesto re-POST)create-nullifier-created-at-index.sql(CONCURRENTLY+ validity gates)Tests: fresh-stack 38/38 (window semantics, both-direction raw/rollup parity, index builds under live writes, real Hasura webhook delivery, end-to-end release runbook), route unit 13/13.
Deployment
Step 0 — pre-flight.
HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=truein prod Hasura;WORLD_ID_ANALYTICS_ROLLUP_ENABLEDabsent orfalsein developer-portal-deployment.Step 1 — build the indexes before merging this PR. From this PR branch, run the operation directly with
psql(not through Hasura migrations or inside a transaction):psql "$PROD_DATABASE_URL" -X -v ON_ERROR_STOP=1 \ -f hasura/operations/world-id-analytics/create-nullifier-created-at-index.sqlBoth source tables already exist on
main. Wait for both concurrent index builds to finish successfully before merging.Step 2 — merge this PR. The cron registers, but every tick returns
disabledwithout touching the database, and nothing reads the new tables yet.Step 3 — backfill all history.
nullifierwas created 2023-02-16, so 2023-02-01 provably predates all data;Re-POST any range that appears in a
failed_ranges(add"chunk_days": 5if it timed out). Done when every response is"failed_ranges": []. Overlaps and full reruns are harmless — every chunk is a delete-and-recount.Step 4 — merge #2192 and #2193.
Step 5 — set
WORLD_ID_ANALYTICS_ROLLUP_ENABLED=truein developer-portal-deployment. The first tick (≤15 min) catches up everything since the backfill; from then on the cron maintains the trailing ~25h unattended.Monitoring. One Datadog alert: no
"Rolled up World ID analytics"success log for >30 min (a dead cron emits nothing). Chunk failures log as errors with their exact range.Rollback / repair. The flag is the kill switch — off stops all recurring work instantly while the UI keeps serving the last-rolled numbers. Any suspect date range, any time: re-POST it. Reverting the PR is safe (additive DDL;
down.sqlexists). The out-of-band indexes remain if the rollout is abandoned; drop them concurrently only if their storage and write overhead are no longer wanted.